/*
 * To change this license header, choose License Headers in Project Properties.
 * To change this template file, choose Tools | Templates
 * and open the template in the editor.
 */

package controladoresBD;

import controladoresBD.exceptions.NonexistentEntityException;
import controladoresBD.exceptions.PreexistingEntityException;
import estrutura.Grupo;
import java.io.Serializable;
import javax.persistence.Query;
import javax.persistence.EntityNotFoundException;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Root;
import estrutura.Materias;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;

/**
 * Controlador para acesso a tabela de Grupo e suas relações.
 * @author Fábio
 */
public class GrupoJpaController implements Serializable {

    /**
     * Constrói um controlador, passando um Entity Manager Factory, que estabele
     * o acesso ao banco de dados.
     * @param emf EntityManagerFactory com a unidade de persistência já escolhida.
     */
    public GrupoJpaController(EntityManagerFactory emf) {
        this.emf = emf;
    }
    
    /**
     * Entity Manager Factory.
     */
    private EntityManagerFactory emf = null;

    /**
     * Cria e retorna um Entity Manager.
     * @return Retorna um Entity Manager.
     */
    public EntityManager getEntityManager() {
        return emf.createEntityManager();
    }

    /**
     * Cria uma tupla na tabela de Grupo e tuplas nas tabelas de relação, se
     * necessário.
     * @param grupo Grupo a ser criado na tabela.
     * @throws PreexistingEntityException Caso já exista uma tupla para o parâmetro.
     * @throws Exception 
     */
    public void create(Grupo grupo) throws PreexistingEntityException, Exception {
        if (grupo.getMaterias() == null) {
            grupo.setMaterias(new ArrayList<Materias>());
        }
        EntityManager em = null;
        try {
            em = getEntityManager();
            em.getTransaction().begin();
            Collection<Materias> attachedMaterias = new ArrayList<>();
            for (Materias materiasMateriasToAttach : grupo.getMaterias()) {
                materiasMateriasToAttach = em.getReference(materiasMateriasToAttach.getClass(), materiasMateriasToAttach.getSigla());
                attachedMaterias.add(materiasMateriasToAttach);
            }
            grupo.setMaterias(attachedMaterias);
            em.persist(grupo);
            for (Materias materiasMaterias : grupo.getMaterias()) {
                materiasMaterias.getContinencias().add(grupo);
                materiasMaterias = em.merge(materiasMaterias);
            }
            em.getTransaction().commit();
        } catch (Exception ex) {
            if (findGrupo(grupo.getId()) != null) {
                throw new PreexistingEntityException("Grupo " + grupo + " already exists.", ex);
            }
            throw ex;
        } finally {
            if (em != null) {
                em.close();
            }
        }
    }

    /**
     * Edição de alguma tupla da tabela.
     * @param grupo Grupo a ser editado na tabela.
     * @throws NonexistentEntityException Caso a tupla não exista.
     * @throws Exception 
     */
    public void edit(Grupo grupo) throws NonexistentEntityException, Exception {
        EntityManager em = null;
        try {
            em = getEntityManager();
            em.getTransaction().begin();
            Grupo persistentGrupo = em.find(Grupo.class, grupo.getId());
            Collection<Materias> materiasOld = persistentGrupo.getMaterias();
            Collection<Materias> materiasNew = grupo.getMaterias();
            Collection<Materias> attachedMateriasNew = new ArrayList<>();
            for (Materias materiasNewMateriasToAttach : materiasNew) {
                materiasNewMateriasToAttach = em.getReference(materiasNewMateriasToAttach.getClass(), materiasNewMateriasToAttach.getSigla());
                attachedMateriasNew.add(materiasNewMateriasToAttach);
            }
            materiasNew = attachedMateriasNew;
            grupo.setMaterias(materiasNew);
            grupo = em.merge(grupo);
            for (Materias materiasOldMaterias : materiasOld) {
                if (!materiasNew.contains(materiasOldMaterias)) {
                    materiasOldMaterias.getContinencias().remove(grupo);
                    materiasOldMaterias = em.merge(materiasOldMaterias);
                }
            }
            for (Materias materiasNewMaterias : materiasNew) {
                if (!materiasOld.contains(materiasNewMaterias)) {
                    materiasNewMaterias.getContinencias().add(grupo);
                    materiasNewMaterias = em.merge(materiasNewMaterias);
                }
            }
            em.getTransaction().commit();
        } catch (Exception ex) {
            String msg = ex.getLocalizedMessage();
            if (msg == null || msg.length() == 0) {
                Integer id = grupo.getId();
                if (findGrupo(id) == null) {
                    throw new NonexistentEntityException("The grupo with id " + id + " no longer exists.");
                }
            }
            throw ex;
        } finally {
            if (em != null) {
                em.close();
            }
        }
    }

    /**
     * Remove uma tupla de acordo com a sua chave primária.
     * @param id ID do Grupo a ser removido.
     * @throws NonexistentEntityException Caso a tupla não exista.
     */
    public void destroy(Integer id) throws NonexistentEntityException {
        EntityManager em = null;
        try {
            em = getEntityManager();
            em.getTransaction().begin();
            Grupo grupo;
            try {
                grupo = em.getReference(Grupo.class, id);
                grupo.getId();
            } catch (EntityNotFoundException enfe) {
                throw new NonexistentEntityException("The grupo with id " + id + " no longer exists.", enfe);
            }
            Collection<Materias> materias = grupo.getMaterias();
            for (Materias materiasMaterias : materias) {
                materiasMaterias.getContinencias().remove(grupo);
                materiasMaterias = em.merge(materiasMaterias);
            }
            em.remove(grupo);
            em.getTransaction().commit();
        } finally {
            if (em != null) {
                em.close();
            }
        }
    }

    /**
     * Procura por todos os grupos.
     * @return Retorna uma lista com todos os grupos do banco de dados.
     */
    public List<Grupo> findGrupoEntities() {
        return findGrupoEntities(true, -1, -1);
    }

    /**
     * Procura os grupos a partir de firstResult até maxResults.
     * @param maxResults Limitante superior para a busca.
     * @param firstResult Limitante inferior para a busca.
     * @return Retorna uma lista com as entidades buscadas.
     */
    public List<Grupo> findGrupoEntities(int maxResults, int firstResult) {
        return findGrupoEntities(false, maxResults, firstResult);
    }

    /**
     * Procura todos os grupos (caso o parâmetro all seja verdadeiro) ou os grupos
     * com índices de firstResult até maxResults.
     * @param all Booleano que indica se retorna todas as matérias ou não.
     * @param maxResults Limitante superior para a busca.
     * @param firstResult Limitante inferior para a busca.
     * @return Retorna uma lista com as entidades buscadas.
     */
    private List<Grupo> findGrupoEntities(boolean all, int maxResults, int firstResult) {
        EntityManager em = getEntityManager();
        try {
            CriteriaQuery cq = em.getCriteriaBuilder().createQuery();
            cq.select(cq.from(Grupo.class));
            Query q = em.createQuery(cq);
            if (!all) {
                q.setMaxResults(maxResults);
                q.setFirstResult(firstResult);
            }
            return q.getResultList();
        } finally {
            em.close();
        }
    }

    /**
     * Procura um grupo de acordo com o seu ID.
     * @param id Chave primária do grupo.
     * @return Retorna a entidade buscada ou null, caso não exista.
     */
    public Grupo findGrupo(Integer id) {
        EntityManager em = getEntityManager();
        try {
            return em.find(Grupo.class, id);
        } finally {
            em.close();
        }
    }

    /**
     * Conta a quantidade de grupos existentes no banco de dados.
     * @return Retorna a quantidade de grupos que existem no banco de dados.
     */
    public int getGrupoCount() {
        EntityManager em = getEntityManager();
        try {
            CriteriaQuery cq = em.getCriteriaBuilder().createQuery();
            Root<Grupo> rt = cq.from(Grupo.class);
            cq.select(em.getCriteriaBuilder().count(rt));
            Query q = em.createQuery(cq);
            return ((Long) q.getSingleResult()).intValue();
        } finally {
            em.close();
        }
    }
    
}
